1 /***
2 * Redistribution and use of this software and associated documentation
3 * ("Software"), with or without modification, are permitted provided
4 * that the following conditions are met:
5 *
6 * 1. Redistributions of source code must retain copyright
7 * statements and notices. Redistributions must also contain a
8 * copy of this document.
9 *
10 * 2. Redistributions in binary form must reproduce the
11 * above copyright notice, this list of conditions and the
12 * following disclaimer in the documentation and/or other
13 * materials provided with the distribution.
14 *
15 * 3. The name "Exolab" must not be used to endorse or promote
16 * products derived from this Software without prior written
17 * permission of Exoffice Technologies. For written permission,
18 * please contact info@exolab.org.
19 *
20 * 4. Products derived from this Software may not be called "Exolab"
21 * nor may "Exolab" appear in their names without prior written
22 * permission of Exoffice Technologies. Exolab is a registered
23 * trademark of Exoffice Technologies.
24 *
25 * 5. Due credit should be given to the Exolab Project
26 * (http://www.exolab.org/).
27 *
28 * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
32 * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39 * OF THE POSSIBILITY OF SUCH DAMAGE.
40 *
41 * Copyright 2004-2005 (C) Exoffice Technologies Inc. All Rights Reserved.
42 *
43 * $Id: HTTPInputStream.java,v 1.3 2005/04/04 15:08:52 tanderson Exp $
44 */
45 package org.exolab.jms.net.http;
46
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.net.HttpURLConnection;
50 import java.net.URL;
51
52 import org.apache.commons.logging.Log;
53 import org.apache.commons.logging.LogFactory;
54
55
56 /***
57 * Reads and buffers input from an {@link java.net.URLConnection}.
58 *
59 * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
60 * @version $Revision: 1.3 $ $Date: 2005/04/04 15:08:52 $
61 */
62 class HTTPInputStream extends InputStream {
63
64 /***
65 * The connection identifier.
66 */
67 private final String _id;
68
69 /***
70 * The tunnel servlet URL.
71 */
72 private final URL _url;
73
74 /***
75 * The connection information.
76 */
77 private final HTTPRequestInfo _info;
78
79 /***
80 * The input stream from the servlet.
81 */
82 private InputStream _in;
83
84 /***
85 * The local data buffer.
86 */
87 private byte[] _data = new byte[1024];
88
89 /***
90 * Temporary buffer for single byte reads.
91 */
92 private final byte[] _byte = new byte[1];
93
94 /***
95 * The index into <code>_data</code> where data starts.
96 */
97 private int _index = 0;
98
99 /***
100 * The number of available bytes in <code>_data</code>.
101 */
102 private int _available = 0;
103
104 /***
105 * The logger.
106 */
107 private static final Log _log = LogFactory.getLog(HTTPInputStream.class);
108
109 /***
110 * Construct a new <code>HTTPInputStream</code>.
111 *
112 * @param id the connection identifier
113 * @param url the URL to connect to
114 * @param info the connection information
115 */
116 public HTTPInputStream(String id, URL url, HTTPRequestInfo info) {
117 _id = id;
118 _url = url;
119 _info = info;
120 }
121
122 /***
123 * Reads the next byte of data from the input stream. The value byte is
124 * returned as an <code>int</code> in the range <code>0</code> to
125 * <code>255</code>. If no byte is available because the end of the stream
126 * has been reached, the value <code>-1</code> is returned. This method
127 * blocks until input data is available, the end of the stream is detected,
128 * or an exception is thrown.
129 * <p/>
130 * <p> A subclass must provide an implementation of this method.
131 *
132 * @return the next byte of data, or <code>-1</code> if the end of the
133 * stream is reached.
134 * @throws IOException if an I/O error occurs.
135 */
136 public int read() throws IOException {
137 final int mask = 0xFF;
138 int count = read(_byte, 0, 1);
139 return (count == 1) ? _byte[0] & mask : -1;
140 }
141
142 /***
143 * Reads up to <code>length</code> bytes of data from the input stream into
144 * an array of bytes. An attempt is made to read as many as
145 * <code>length</code> bytes, but a smaller number may be read, possibly
146 * zero. The number of bytes actually read is returned as an integer.
147 * <p/>
148 * <p> If the first byte cannot be read for any reason other than end of
149 * file, then an <code>IOException</code> is thrown. In particular, an
150 * <code>IOException</code> is thrown if the input stream has been closed.
151 *
152 * @param buffer the buffer into which the data is read
153 * @param offset the start offset in array <code>buffer</code> at which the
154 * data is written
155 * @param length the maximum number of bytes to read
156 * @return the total number of bytes read into the buffer, or
157 * <code>-1</code> if there is no more data because the end of the
158 * stream has been reached.
159 * @throws IOException if an I/O error occurs.
160 * @throws IndexOutOfBoundsException if <code>offset</code> is negative, or
161 * <code>length</code> is negative, or
162 * <code>offset+length</code> is greater
163 * than the length of the array
164 * @throws NullPointerException if <code>buffer</code> is null
165 */
166 public int read(byte[] buffer, int offset, int length) throws IOException {
167 int count = 0;
168 if (offset < 0 || length < 0) {
169 throw new IndexOutOfBoundsException();
170 }
171 if (length > 0) {
172 if (_available == 0) {
173 try {
174 doRead();
175 } catch (IOException exception) {
176 _log.debug(exception, exception);
177 throw exception;
178 }
179 }
180 count = (length <= _available) ? length : _available;
181 if (_log.isDebugEnabled()) {
182 _log.debug("read(length=" + length + "), [id=" + _id
183 + ", available=" + _available + "]");
184 }
185
186 if (count > 0) {
187
188 System.arraycopy(_data, _index, buffer, offset, count);
189 _index += count;
190 _available -= count;
191 }
192 }
193 return count;
194 }
195
196 /***
197 * Read from the connection and cache locally.
198 *
199 * @return the total number of bytes read into the buffer, or
200 * <code>-1</code> if there is no more data because the end of the
201 * stream has been reached.
202 * @throws IOException if the read fails
203 */
204 protected int doRead() throws IOException {
205 int count = 0;
206 boolean done = false;
207 while (!done) {
208 if (_in == null) {
209 connect();
210 done = true;
211 }
212 count = _in.read(_data);
213 if (count != -1) {
214 _available = count;
215 _index = 0;
216 done = true;
217 } else {
218 _in.close();
219 _in = null;
220 }
221 }
222 return count;
223 }
224
225 /***
226 * Connect to the tunnel servlet and get the input stream.
227 *
228 * @throws IOException for any I/O error
229 */
230 private void connect() throws IOException {
231 int length = 0;
232 HttpURLConnection connection = null;
233 while (length == 0) {
234
235
236 connection = TunnelHelper.connect(_url, _id, "read", _info);
237 length = connection.getContentLength();
238 if (length == -1) {
239
240
241
242 } else if (length == 0) {
243 try {
244
245 Thread.sleep(1000);
246 } catch (InterruptedException ignore) {
247 }
248 }
249 }
250 _in = connection.getInputStream();
251 if (_log.isDebugEnabled()) {
252 _log.debug("connect(), [id=" + _id
253 + ", contentLength=" + length + "]");
254 }
255 }
256 }